/*
 * opt_constant_folding.c
 *
 * Implements a constant folding optimization
 *
 * Edited by: Jeremy Hansen, RANDY Compton
 *
 * We fold any constants within individual blocks.
 */

#include "opt_constant_folding.h"

// Prototypes
void constant_fold(struct tac_t *ss);
void remove_constant(struct tac_t **cs, char *name);
struct tac_t *find_constant(struct tac_t *cs, char *name);

/* -----------------------------------------------------------------------
 * Applies the constant folding optimization.
 * -----------------------------------------------------------------------
 */
void opt_constant_folding(struct ir_list_t *ir_list)
{
	struct ir_list_t *temp = ir_list;
	while (temp != NULL) {
		struct ir_t *ir = temp->ir;
		// Fold constants within each block
		while(ir != NULL) {
			constant_fold(ir->statements);
			ir = ir->next;
		}
		temp = temp->next;
	}
}

/* -----------------------------------------------------------------------
 * Applies the constant folding optimization to a basic block
 * -----------------------------------------------------------------------
 */
void constant_fold(struct tac_t *ss) {
	struct tac_t *constants = NULL; // Storage for known constants
	for(; ss != NULL; ss = ss->next) {
		if(ss->v1 != NULL) {
			remove_constant(&constants, ss->v1);
			if(ss->type == TAC_SET && ss->v2 == NULL) // Is it a defined constant?
				const_statement_new(&constants, TAC_SET, ss->v1, ss->n2); // Store it
			else {
				struct tac_t *a1 = NULL, *a2 = NULL;
				a1 = find_constant(constants, ss->v2);
				if(a1 != NULL && ss->v3 != NULL) {
					a2 = find_constant(constants, ss->v3);
					if(a2 == NULL) continue;
					switch(ss->type) {
						case TAC_PLUS: ss->v2 = NULL; ss->v3 = NULL; // Remove the names
							ss->n2 = a1->n2 + a2->n2; break; // Perform the operation
						case TAC_MINUS: ss->v2 = NULL; ss->v3 = NULL; // Remove the names
							ss->n2 = a1->n2 - a2->n2; break; // Perform the operation
						case TAC_TIMES: ss->v2 = NULL; ss->v3 = NULL; // Remove the names
							ss->n2 = a1->n2 * a2->n2; break; // Perform the operation
						case TAC_DIV: ss->v2 = NULL; ss->v3 = NULL; // Remove the names
							ss->n2 = a1->n2 / a2->n2; break; // Perform the operation
						case TAC_MOD: ss->v2 = NULL; ss->v3 = NULL; // Remove the names
							ss->n2 = a1->n2 % a2->n2; break; // Perform the operation
						default: continue;
					}
					ss->type = TAC_SET; // Mark it as a (constant) assignment
					const_statement_new(&constants, TAC_SET, ss->v1, ss->n2); // Store it
				}
			}
		}
	}

	// Free the constant list
	while(constants != NULL) {
		struct tac_t *temp = constants;
		constants = temp->next;
		free(temp);
	}
}

/* -----------------------------------------------------------------------
 * Remove a constant definition from a list
 * -----------------------------------------------------------------------
 */
void remove_constant(struct tac_t **cs, char *name) {
	while(*cs != NULL) {
		if(strcasecmp((*cs)->v1, name) == 0) { // Did we find it?
			struct tac_t *temp = *cs;
			*cs = temp->next; // Unlink it from the list
			free(temp);
			break;
		}
		cs = &(*cs)->next;
	}
}

/* -----------------------------------------------------------------------
 * Find a constant definition in a list
 * -----------------------------------------------------------------------
 */
struct tac_t *find_constant(struct tac_t *cs, char *name) {
	while(cs != NULL) {
		if(strcasecmp(cs->v1, name) == 0) return cs; // Did we find it?
		cs = cs->next;
	}
	return NULL;
}
